﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace bxml.betterxml
{
    public abstract class ExpatLib : IDisposable
    {
        #region Member variables

        protected const string NaN = "None";

        protected IntPtr XmlParser;
        protected GCHandle ParserHandle;

        private bool IsDisposed = false;

        #endregion

        #region Lib Expat Const define


        public enum XMLBool : byte
        {
            FALSE = 0,
            TRUE = 1
        }

        public enum XMLStatus : int
        {
            ERROR = 0,
            OK,
            SUSPENDED
        };

        /**<summary>Represents XML_Error enum in Expat library.</summary>
 * <remarks>Must be in sync with <see cref="XMLErrorSet"/>.</remarks>
 */
        public enum XMLError : int
        {
            NONE,
            NO_MEMORY,
            SYNTAX,
            NO_ELEMENTS,
            INVALID_TOKEN,
            UNCLOSED_TOKEN,
            PARTIAL_CHAR,
            TAG_MISMATCH,
            DUPLICATE_ATTRIBUTE,
            JUNK_AFTER_DOC_ELEMENT,
            PARAM_ENTITY_REF,
            UNDEFINED_ENTITY,
            RECURSIVE_ENTITY_REF,
            ASYNC_ENTITY,
            BAD_CHAR_REF,
            BINARY_ENTITY_REF,
            ATTRIBUTE_EXTERNAL_ENTITY_REF,
            MISPLACED_XML_PI,
            UNKNOWN_ENCODING,
            INCORRECT_ENCODING,
            UNCLOSED_CDATA_SECTION,
            EXTERNAL_ENTITY_HANDLING,
            NOT_STANDALONE,
            UNEXPECTED_STATE,
            ENTITY_DECLARED_IN_PE,
            FEATURE_REQUIRES_XML_DTD,
            CANT_CHANGE_FEATURE_ONCE_PARSING,
            UNBOUND_PREFIX,
            UNDECLARING_PREFIX,
            INCOMPLETE_PE,
            XML_DECL,
            TEXT_DECL,
            PUBLICID,
            SUSPENDED,
            NOT_SUSPENDED,
            ABORTED,
            FINISHED,
            SUSPEND_PE,
            RESERVED_PREFIX_XML,
            RESERVED_PREFIX_XMLNS,
            RESERVED_NAMESPACE_URI

        };

        #endregion

        #region LibExpat Native code

#if MONO
        public const string expatLib = "expatw";
#else
        public const string expatLib = "libexpatw.dll";
#endif


        #region call back type in Expat Library


        /**<summary>Represents XML_StartElementHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLStartElementHandler(IntPtr userData, char* name, char** atts);

        /**<summary>Represents XML_EndElementHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLEndElementHandler(IntPtr userData, char* name);

        /**<summary>Represents XML_CharacterDataHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLCharacterDataHandler(IntPtr userData, char* s, int len);

        /**<summary>Represents XML_ProcessingInstructionHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLProcessingInstructionHandler(IntPtr userData, char* target, char* data);

        /**<summary>Represents XML_CommentHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLCommentHandler(IntPtr userData, char* data);

        /**<summary>Represents XML_StartCdataSectionHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLStartCdataSectionHandler(IntPtr userData);

        /**<summary>Represents XML_EndCdataSectionHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLEndCdataSectionHandler(IntPtr userData);

        /**<summary>Represents XML_StartNamespaceDeclHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLStartNamespaceDeclHandler(IntPtr userData, char* prefix, char* uri);

        /**<summary>Represents XML_EndNamespaceDeclHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void XMLEndNamespaceDeclHandler(IntPtr userData, char* prefix);

        /**<summary>Represents XML_EntityDeclHandler call-back type in Expat library.</summary> */
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public unsafe delegate void
        XMLEntityDeclHandler(IntPtr userData,
                             char* entityName,
                             int isParameterEntity,
                             char* value,
                             int valueLen,
                             char* baseUri,
                             char* systemId,
                             char* publicId,
                             char* notationName);

        #endregion

        [DllImport(expatLib,
                   EntryPoint = "XML_ParserFree",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLParserFree(IntPtr parser);

        [DllImport(expatLib,
                  EntryPoint = "XML_ParserCreateNS",
                  CharSet = CharSet.Unicode,
                  CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr XMLParserCreateNS(string encoding, char namespaceSeparator);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetReturnNSTriplet",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetReturnNSTriplet(IntPtr parser, int returnNSTriplet);

        [DllImport(expatLib,
                  EntryPoint = "XML_Parse",
                  CharSet = CharSet.Unicode,
                  CallingConvention = CallingConvention.Cdecl)]
        public static extern XMLStatus XMLParse(IntPtr parser, [In, MarshalAs(UnmanagedType.LPArray)] byte[] s, int len, int isFinal);

        [DllImport(expatLib,
                   EntryPoint = "XML_ParserReset",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern XMLBool XMLParserReset(IntPtr parser, string encoding);

        [DllImport(expatLib,
                          EntryPoint = "XML_SetElementHandler",
                          CharSet = CharSet.Unicode,
                          CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetElementHandler(IntPtr parser, XMLStartElementHandler start, XMLEndElementHandler end);

        [DllImport(expatLib,
                 EntryPoint = "XML_SetStartElementHandler",
                 CharSet = CharSet.Unicode,
                 CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetStartElementHandler(IntPtr parser, XMLStartElementHandler handler);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetEndElementHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetEndElementHandler(IntPtr parser, XMLEndElementHandler handler);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetCharacterDataHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetCharacterDataHandler(IntPtr parser, XMLCharacterDataHandler handler);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetProcessingInstructionHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetProcessingInstructionHandler(IntPtr parser, XMLProcessingInstructionHandler handler);


        [DllImport(expatLib,
                  EntryPoint = "XML_SetCommentHandler",
                  CharSet = CharSet.Unicode,
                  CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetCommentHandler(IntPtr parser, XMLCommentHandler handler);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetCdataSectionHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetCdataSectionHandler(IntPtr parser, XMLStartCdataSectionHandler start, XMLEndCdataSectionHandler end);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetStartCdataSectionHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetStartCdataSectionHandler(IntPtr parser, XMLStartCdataSectionHandler start);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetEndCdataSectionHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetEndCdataSectionHandler(IntPtr parser, XMLEndCdataSectionHandler end);

        [DllImport(expatLib,
                  EntryPoint = "XML_SetNamespaceDeclHandler",
                  CharSet = CharSet.Unicode,
                  CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetNamespaceDeclHandler(IntPtr parser, XMLStartNamespaceDeclHandler start, XMLEndNamespaceDeclHandler end);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetStartNamespaceDeclHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetStartNamespaceDeclHandler(IntPtr parser, XMLStartNamespaceDeclHandler start);

        [DllImport(expatLib,
                   EntryPoint = "XML_SetEndNamespaceDeclHandler",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetEndNamespaceDeclHandler(IntPtr parser, XMLEndNamespaceDeclHandler end);


        [DllImport(expatLib,
                   EntryPoint = "XML_SetUserData",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern void XMLSetUserData(IntPtr parser, IntPtr userData);

        [DllImport(expatLib,
                   EntryPoint = "XML_GetBuffer",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        unsafe public static extern byte* XMLGetBuffer(IntPtr parser, int len);


        [DllImport(expatLib,
                   EntryPoint = "XML_ParseBuffer",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern XMLStatus XMLParseBuffer(IntPtr parser, int len, int isFinal);


        [DllImport(expatLib,
                   EntryPoint = "XML_GetErrorCode",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern XMLError XMLGetErrorCode(IntPtr parser);

        [DllImport(expatLib,
                   EntryPoint = "XML_ErrorString",
                   CharSet = CharSet.Unicode,
                   CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr _XMLErrorString(XMLError code);

        public static string XMLErrorString(XMLError code)
        {
            IntPtr errPtr = _XMLErrorString(code);
            return Marshal.PtrToStringUni(errPtr);
        }

        #endregion

        #region Event Attribute

        /* StartElementHandler */
        private XMLStartElementHandler startElementHandler;

        /// <summary>Handler for start tag.</summary>
        public XMLStartElementHandler StartElementHandler
        {
            get
            {
                return startElementHandler;
            }
            set
            {
                CheckNotDisposed();
                startElementHandler = value;
                XMLSetStartElementHandler(XmlParser, value);
            }
        }

        /* EndElementHandler */
        private XMLEndElementHandler endElementHandler;

        /// <summary>Handler for end tag.</summary>
        public XMLEndElementHandler EndElementHandler
        {
            get
            {
                return endElementHandler;
            }
            set
            {
                CheckNotDisposed();
                endElementHandler = value;
                XMLSetEndElementHandler(XmlParser, value);
            }
        }

        /* CharacterDataHandler */
        private XMLCharacterDataHandler characterDataHandler;

        /// <summary>Handler for character data.</summary>
        public XMLCharacterDataHandler CharacterDataHandler
        {
            get
            {
                return characterDataHandler;
            }
            set
            {
                CheckNotDisposed();
                characterDataHandler = value;
                XMLSetCharacterDataHandler(XmlParser, value);
            }
        }

        /* ProcessingInstructionHandler */
        private XMLProcessingInstructionHandler processingInstructionHandler;

        /// <summary>Handler for processing instructions.</summary>
        public XMLProcessingInstructionHandler ProcessingInstructionHandler
        {
            get
            {
                return processingInstructionHandler;
            }
            set
            {
                CheckNotDisposed();
                processingInstructionHandler = value;
                XMLSetProcessingInstructionHandler(XmlParser, value);
            }
        }


        /* XMLCommentHandler */
        private XMLCommentHandler commentHandler;
        public XMLCommentHandler CommentHandler
        {
            get
            {
                return commentHandler;
            }
            set
            {
                CheckNotDisposed();
                commentHandler = value;
                XMLSetCommentHandler(XmlParser, value);
            }
        }

        /* StartNamespaceDeclHandler */
        private XMLStartNamespaceDeclHandler startNamespaceDeclHandler;

        /// <summary>Handler called when namespace scope starts.</summary>
        public XMLStartNamespaceDeclHandler StartNamespaceDeclHandler
        {
            get
            {
                return startNamespaceDeclHandler;
            }
            set
            {
                CheckNotDisposed();
                startNamespaceDeclHandler = value;
                XMLSetStartNamespaceDeclHandler(XmlParser, value);
            }
        }

        /* EndNamespaceDeclHandler */
        private XMLEndNamespaceDeclHandler endNamespaceDeclHandler;

        /// <summary>Handler called when namespace scope ends.</summary>
        public XMLEndNamespaceDeclHandler EndNamespaceDeclHandler
        {
            get
            {
                return endNamespaceDeclHandler;
            }
            set
            {
                CheckNotDisposed();
                endNamespaceDeclHandler = value;
                XMLSetEndNamespaceDeclHandler(XmlParser, value);
            }
        }

        protected virtual void CheckNotDisposed()
        {
            if (IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
        }

        #endregion

        public void Dispose()
        {
            FreeParser();
            Cleanup();
            // resources cleaned up - no need to have object finalized
            GC.SuppressFinalize(this);
            IsDisposed = true;
        }

        private void FreeParser()
        {
            if (XmlParser != IntPtr.Zero)
            {
                XMLParserFree(XmlParser);
                XmlParser = IntPtr.Zero;
            }
        }

        protected virtual void Cleanup()
        {
            if (ParserHandle.IsAllocated)
            {
                ParserHandle.Free();
            }
        }

        unsafe protected virtual void InitParser(string encoding)
        {
            if (XmlParser != IntPtr.Zero)
            {
                XMLParserReset(XmlParser, encoding);
            }
            else
            {
                FreeParser();
                XmlParser = XMLParserCreateNS(encoding, ExpatUtil.NSSep);

                StartElementHandler = OnStartElementEvent;
                EndElementHandler = OnEndElementEvent;

                CharacterDataHandler = OnCharacterEvent;
                CommentHandler = OnCommentHandler;

                StartNamespaceDeclHandler = OnStartPrefixMappingEvent;
                EndNamespaceDeclHandler = OnEndElementEvent;

                ProcessingInstructionHandler = OnProcessingInstructionEvent;
            }

            XMLSetElementHandler(XmlParser, StartElementHandler, EndElementHandler);
            XMLSetCharacterDataHandler(XmlParser, CharacterDataHandler);
            XMLSetStartNamespaceDeclHandler(XmlParser, StartNamespaceDeclHandler);
            XMLSetEndNamespaceDeclHandler(XmlParser, EndNamespaceDeclHandler);
            XMLSetProcessingInstructionHandler(XmlParser, ProcessingInstructionHandler);

            ParserHandle = GCHandle.Alloc(this, GCHandleType.WeakTrackResurrection);
            XMLSetUserData(XmlParser, (IntPtr)ParserHandle);
        }

        #region Implement Org.System.Xml.Sax.IContentHandler

        private static unsafe void OnStartElementEvent(IntPtr userData, char* name, char** atts)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.StartElement(ExpatUtil.PointerToString(name), ExpatUtil.PointerToStringArray(atts));
        }

        protected virtual void StartElement(string name, string[] atts)
        {
        }

        private static unsafe void OnEndElementEvent(IntPtr userData, char* name)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.EndElement(ExpatUtil.PointerToString(name));
        }

        protected virtual void EndElement(string name)
        {
        }

        private static unsafe void OnProcessingInstructionEvent(IntPtr userData, char* target, char* data)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.ProcessingInstruction(ExpatUtil.PointerToString(target), ExpatUtil.PointerToString(data));
        }

        protected virtual void ProcessingInstruction(string target, string data)
        {

        }

        private static unsafe void OnCharacterEvent(IntPtr userData, char* s, int length)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.Characters(length, ExpatUtil.PointerToString(s, length));
        }

        protected virtual void Characters(int length, string data)
        {
        }

        private static unsafe void OnCommentHandler(IntPtr userData, char* s)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.Comment(ExpatUtil.PointerToString(s));
        }

        protected virtual void Comment(string data)
        {

        }

        private static unsafe void OnStartPrefixMappingEvent(IntPtr userData, char* prefix, char* uri)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.StartPrefixMapping(ExpatUtil.PointerToString(prefix), ExpatUtil.PointerToString(uri));
        }

        protected virtual void StartPrefixMapping(string prefix, string uri)
        {

        }

        private static unsafe void OnEndPrefixMappingEvent(IntPtr userData, char* prefix)
        {
            ExpatLib reader = (ExpatLib)((GCHandle)userData).Target;
            reader.EndPrefixMapping(ExpatUtil.PointerToString(prefix));
        }

        protected virtual void EndPrefixMapping(string prefix)
        {

        }

        #endregion

    }
}
